package com.noodles.mall.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.noodles.mall.entity.Item;
import com.noodles.mall.entity.ItemRecommend;
import com.noodles.mall.mapper.ItemMapper;
import com.noodles.mall.mapper.ItemRecommendMapper;
import com.noodles.mall.service.ItemRecommendService;
import com.noodles.mall.util.ChainMap;
import com.noodles.mall.vo.PageVo;
import org.apache.commons.math3.linear.MatrixUtils;
import org.apache.commons.math3.linear.RealMatrix;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class ItemRecommendServiceImpl extends ServiceImpl<ItemRecommendMapper, ItemRecommend> implements ItemRecommendService {

    @Autowired
    private ItemRecommendService itemRecommendService;

    @Autowired(required = false)
    private ItemMapper itemMapper;

    /**
     * 查询某用户的推荐商品列表
     * @param userId
     * @param current
     * @param size
     * @return pageVo
     */
    @Override
    public PageVo selectRecommend(Integer userId, Long current, Long size) {
        Map<String, Object> map = itemRecommendService.getItemSimilarityMatrix();
        double[][] itemSimilarityMatrix = (double[][]) map.get("matrix");
        int[] itemIdRecord = (int[]) map.get("record");
        int matrixLength = itemSimilarityMatrix.length;
        Set<Integer> itemIdSet = new HashSet<>();   // 记录与用户喜欢的商品相似的商品id
        LambdaQueryWrapper<ItemRecommend> wrapper = new QueryWrapper<ItemRecommend>().lambda();
        wrapper=wrapper.ge(ItemRecommend::getScore, 5).eq(ItemRecommend::getUserId, userId);
        List<ItemRecommend> itemRecommendList = itemRecommendService.list(wrapper);  // score > 5 作为用户喜欢的商品
        itemRecommendList.forEach(itemRecommend -> {
            int row = 0;
            for(int n = 0; n < itemIdRecord.length; n++) {
                // 获取用户喜欢的商品在相似矩阵中的位置（第n行）
                if (itemRecommend.getItemId() == itemIdRecord[n]) {
                    row = n;
                }
            }
            for (int i = 0; i < matrixLength; i++) {
                if (i == row) continue;
                // 若相似度超过0.35则推荐
                if (itemSimilarityMatrix[row][i] >= 0.35) {
                    itemIdSet.add(itemIdRecord[i]);
                }
            }
        });
        // 如果不够条数就随机补上
        while (itemIdSet.size() < size - current) {
            int randomNum = new Random().nextInt(matrixLength - 1);
            itemIdSet.add(itemIdRecord[randomNum]);
        }
        Page<Item> page = itemMapper.selectPage(new Page<>(current, size), new QueryWrapper<Item>().lambda().in(Item::getId, itemIdSet));
        PageVo<Item> pageVo = new PageVo<>();
        BeanUtils.copyProperties(page, pageVo);
        return pageVo;
    }

    @Cacheable(value = "item-similarity-matrix")
    @Override
    public Map<String, Object> getItemSimilarityMatrix() {
        Map<String, Object> map = getUserCountMatrix();
        RealMatrix userCountMatrix = (RealMatrix) map.get("matrix");
        double[][] itemSimilarityMatrix = getItemSimilarityMatrix(userCountMatrix);
        map.put("matrix", itemSimilarityMatrix);
        return map;
    }

    /**
     * 建立商品相似度矩阵
     *
     * @param userCountMatrix
     * @return
     */
    @Cacheable(value = "similarity-matrix")
    public double[][] getItemSimilarityMatrix(RealMatrix userCountMatrix) {
        int size = userCountMatrix.getRowDimension();
        double[][] similarityMatrix = new double[size][size];
        for (int i = 0; i < size; i++) {
            double n_i = userCountMatrix.getColumnMatrix(i).getNorm();
            for (int j = 0; j < size; j++) {
                if (i == j) continue;
                double n_j = userCountMatrix.getColumnMatrix(j).getNorm();
                similarityMatrix[i][j] = userCountMatrix.getEntry(i, j) / Math.sqrt(n_i * n_j);
            }
        }
        return similarityMatrix;
    }

    @Cacheable(value = "user-count-matrix")
    public Map<String, Object> getUserCountMatrix() {
        List<ItemRecommend> itemRecommendList = itemRecommendService.list();
        // 记录喜欢商品的用户
        HashMap<Integer, Set<Integer>> map = new HashMap<>();
        for (ItemRecommend itemRecommend : itemRecommendList) {
            Integer itemId = itemRecommend.getItemId();
            Set<Integer> userIdSet = map.getOrDefault(itemId, new HashSet<>());
            userIdSet.add(itemRecommend.getUserId());
            map.put(itemId, userIdSet);
        }
        int size = map.size();
        int[] itemIdRecord = new int[size];   // 索引作为商品在矩阵中的位置，值作itemId
        // 建立矩阵记录喜欢两两商品的用户数
        RealMatrix realMatrix = MatrixUtils.createRealMatrix(size, size);
        int row = 0, column = 0;
        for (Integer k1 : map.keySet()) {
            itemIdRecord[row] = k1;
            Set<Integer> userIdSet = map.get(k1);
            for (Integer k2 : map.keySet()) {
                Set<Integer> set = new HashSet<>(map.get(k2));
                set.retainAll(userIdSet);   // 取交集，即得到喜欢两两商品的用户
                realMatrix.setEntry(row, column, set.size());
                column++;
            }
            row++;
            column = 0;
        }
        return ChainMap.instance().put("matrix", realMatrix).put("record", itemIdRecord);
    }
}
